package cluster

import (
	"github.com/gogo/protobuf/proto"
	"leaf/core/conf"
	"leaf/core/log"
	"leaf/core/rpc"
	"leaf/core/tool"
	"math/rand"
	"sync"
)

type NodeStatus struct {
	ServerId      int32
	ServerType    int32
	ServerTypeStr string
	RpcAddr       string
	Load          int32
}

var status = struct {
	sync.RWMutex
	NodeStatus
}{}

var nodesMgr *NodesManager = NewNodeManager()

func GetNodeId(status *NodeStatus) string {
	return tool.Itoa(status.ServerId)
}

type Node struct {
	*NodeStatus
	*rpc.RpcClient
}

func NewNodeInfo(status *NodeStatus) *Node {
	info := new(Node)
	info.NodeStatus = status
	info.RpcClient = rpc.Connect(info.RpcAddr, GetNodeId(status), nodesMgr)
	log.Release("new node rpc connect addr:%v serverId:%v serverType:%v", status.RpcAddr, GetNodeId(status), status.ServerTypeStr)
	return info
}

func (node *Node) Destroy() {
	node.RpcClient.Destroy()
}

func (node *Node) Call(method string, request proto.Message, reply proto.Message) error {
	if node.ServerId == conf.Config.ServerId {
		return rpc.LocalCall(method, request, reply)
	}

	return node.RpcClient.Call(method, request, reply)
}

func (node *Node) Cast(method string, request proto.Message) error {
	if node.ServerId == conf.Config.ServerId {
		return rpc.LocalCast(method, request)
	}

	return node.RpcClient.Cast(method, request)
}

type NodesManager struct {
	sync.RWMutex
	nodes map[int32]map[int32]*Node
}

func NewNodeManager() (m *NodesManager) {
	return &NodesManager{
		nodes: map[int32]map[int32]*Node{},
	}
}

func (n *NodesManager) AddOrUpdateNode(status *NodeStatus) {
	if status == nil {
		return
	}
	flag := false
	for _, severTypeStr := range conf.Config.RpcServers {
		if severTypeStr == status.ServerTypeStr && conf.Config.ServerId != status.ServerId {
			flag = true
		}
	}
	if !flag {
		return
	}

	n.Lock()
	defer n.Unlock()
	if _, exist := n.nodes[status.ServerType]; !exist {
		n.nodes[status.ServerType] = map[int32]*Node{}
	}

	if info, ok := n.nodes[status.ServerType][status.ServerId]; ok {
		info.NodeStatus = status
	} else {
		n.nodes[status.ServerType][status.ServerId] = NewNodeInfo(status)
	}
}

func (n *NodesManager) Destroy() {
	var delNodes []*Node

	n.Lock()
	for _, nodes := range n.nodes {
		for id, node := range nodes {
			delete(nodes, id)
			delNodes = append(delNodes, node)
		}
	}
	defer n.Unlock()

	for _, node := range delNodes {
		node.Destroy()
	}
}

func (n *NodesManager) DeleteNode(uid string) {
	var sid = int32(tool.Atoi(uid))
	var stype = sid / 1000
	var node *Node = nil

	n.Lock()
	if nodes := n.nodes[stype]; nodes != nil {
		node = nodes[sid]
		delete(nodes, sid)
	}
	n.Unlock()

	if node != nil {
		node.Destroy()
	}
}

func (n *NodesManager) OnRpcDisconnect(uniqueid string) {
	n.DeleteNode(uniqueid)
}

func (n *NodesManager) Dump() {
	for k, v := range n.nodes {
		for kk, vv := range v {
			log.Release("NodesManager Name:%s Id:%s Node:%+v", k, kk, vv)
		}
	}
}

func (n *NodesManager) GetRandNode(servType int32) *Node {
	n.RLock()
	var nodeArray []*Node
	for _, v := range n.nodes[servType] {
		if v.GetReady() {
			nodeArray = append(nodeArray, v)
		}
	}
	n.RUnlock()

	if arrayLen := len(nodeArray); arrayLen > 0 {
		index := rand.Intn(len(nodeArray))
		for _, v := range nodeArray {
			if index == 0 {
				return v
			}
			index--
		}
	}
	return nil
}

func (n *NodesManager) GetNode(sid int32) *Node {
	var stype int32 = sid / 1000
	n.RLock()
	if nodes, _ := n.nodes[stype]; nodes != nil {
		node, _ := nodes[sid]
		if !node.GetReady() {
			node = nil
		}
		n.RUnlock()
		return node
	}
	n.RUnlock()
	return nil
}

func (n *NodesManager) GetNodes(stype int32) []*Node {
	nl := []*Node{}
	n.RLock()
	for _, v := range n.nodes[stype] {
		if v.GetReady() {
			nl = append(nl, v)
		}
	}
	n.RUnlock()
	return nl
}
